The @Cacheable
and @CacheFlush
annotations can be applied to controller actions and the plugin will then cache the page fragment generated by the controller whether this is done by rendering a GSP, using a MarkupBuilder closure, rendering text directly or whatever. Only successful page renders are cached, so redirects, 404s, errors and so on will not be.Composing pages so that they can be optimally cached requires some thought. The plugin uses a servlet filter that runs 'inside' the SiteMesh filter provided by Grails. This means that cached output is decorated by SiteMesh and the resulting page can therefore contain uncached content from the SiteMesh template. In addition you can use caching at a modular level to cache the output of controller actions invoked using the g:include
tag, or by caching taglib tags. Combining these techniques leads to powerful modular page caching. For example, you can cache the output of the 'main' controller then use g:include
tags, or taglib tags, in the SiteMesh layout to include content on the page that is cached separately - and can be flushed separately - from the main body of the page.
Example: caching Grails CRUD pages
Grails' standard scaffolded CRUD pages provide a good example of how caching and flushing can be applied. For example, let's take an Album domain class. The scaffolded controller could be annotated like this:AlbumController.groovy
class AlbumController {
// the index action is uncached as it just performs a redirect to list
def index = {
redirect(action: "list", params: params)
} @Cacheable("albumControllerCache")
def list = {
// standard Grails scaffolding code omitted
} @Cacheable("albumControllerCache")
def create = {
// standard Grails scaffolding code omitted
} @CacheFlush(["albumControllerCache", "artistControllerCache", "latestControllerCache", "popularControllerCache"])
def save = {
// standard Grails scaffolding code omitted
} @Cacheable("albumControllerCache")
def show = {
// standard Grails scaffolding code omitted
} @Cacheable("albumControllerCache")
def edit = {
// standard Grails scaffolding code omitted
} @CacheFlush(["albumControllerCache", "latestControllerCache", "popularControllerCache"])
def update = {
// standard Grails scaffolding code omitted
} @CacheFlush(["albumControllerCache", "artistControllerCache", "latestControllerCache", "popularControllerCache"])
def delete = {
// standard Grails scaffolding code omitted
}
}
The list, show, create and edit pages are all cached. The show and edit rely on an domain object id parameter and this will be included in the cache key so that /album/show/1
and /album/show/2
are cached separately. The save, update and delete actions will flush caches. Note that in addition to flushing the cache used by the list, show, create and edit actions they are flushing other caches which are content caches for controllers whose output should be refreshed if Album
data changes.
Example: decorating a cached page with dynamic content using SiteMesh
It is often necessary to have portions of a page be dynamic. A typical example is when something is displayed to logged in users that will be different for each user. Those sorts of page sections are not really candidates for caching. At the same time other parts of the page may well be able to take advantage of caching. For example, if you want to display a "Welcome back $username" type message in page headers while caching the main body of the page you can use SiteMesh templates like this:grails-app/views/layouts/main.gsp
<html>
<head>
<title><g:layoutTitle default="Welcome to My Grails Application"/></title>
<%-- render the page head from the controller - may be cached --%>
<g:layoutHead/>
</head>
<body>
<%-- render a "welcome back" header (tags used here are from the Spring Security plugin) --%>
<g:isLoggedIn>
<div id="loggedInUser"><g:message code="auth.loggedInAs" args="[loggedInUsername()]" default="Logged in as {0}"/></div>
</g:isLoggedIn>
<g:isNotLoggedIn>
<div id="loginLink"><g:link controller="login"><g:message code="default.login.label" default="Login here"/></g:link></div>
</g:isNotLoggedIn> <%-- render the page body from the controller - may be cached --%>
<g:layoutBody/>
</body>
</html>
If the controller action invoked uses @Cacheable
everything will work fine because the content of the SiteMesh layout is not cached - only the content generated by the cached action. The SiteMesh template is applied to cached and uncached content alike so the correct username will be displayed to your users even though the main body of the page may have been loaded from a cache.Example: a modular page using multiple cached sections
One of the most powerful features of page fragment caching is that the generated page can be composed from multiple cached sections. This is accomplished using Grails' g:include
tag. For example, in this page the main body of the page is rendered by some controller action and the output of other controllers are included in the SiteMesh layout using the g:include
tag:grails-app/views/layouts/main.gsp
<html>
<head>
<title><g:layoutTitle default="Welcome to My Grails Application"/></title>
<%-- render the page head from the controller - may be cached --%>
<g:layoutHead/>
</head>
<body>
<%-- render the page body from the controller - may be cached --%>
<g:layoutBody/> <div class="sidebar">
<%-- each of these controller actions can be cached separately as well --%>
<g:include controller="latest" action="albums"/>
<g:include controller="popular" action="albums"/>
</div>
</body>
</html>
LatestController.groovy
@Cacheable("latestAlbums")
def albums = {
def albums = Album.list(sort: "dateCreated", order: "desc", max: 10)
[albumInstanceList: albums]
}
LatestController.groovy
@Cacheable("popularAlbums")
def albums = {
def albums = Album.listOrderByAverageRating(max: 10)
return [albumInstanceList: albums]
}
If all the caches are hit the final rendered page will be composed of 3 separate cached sections. What is more, each individual section can be flushed without affecting the others so with some thought about how to compose your page and apply your caches you can optimise cache usage without delivering stale data to the user.
The @Cacheable
and @CacheFlush
annotations can be applied to controllers at class level. This is more likely useful with @Cacheable
but it is certainly possible to apply @CacheFlush
at class level so that any action on that controller will flush a set of caches. Any annotation on an individual action will be applied in preference to an annotation at class level, so a class level annotation behaves like a default. An annotation at class level will work with dynamic scaffolded actions so you don't have to generate a concrete action in order to benefit from caching behaviour.@Cacheable("albumControllerCache")
class AlbumController { static scaffold = true // all dynamically scaffolded actions will be cached @Cacheable("albumListCache")
def list = {
// …
} @CacheFlush(/albumw+Cache/)
def save = {
// …
} def show = {
// …
}
}
In this example:
- The show action will use the default class level
@Cacheable
annotation and its page fragment will be cached in the albumControllerCache cache.
- The list action will not use the default as it specifies its own
@Cacheable
annotation and its content will be cached separately.
- The save action uses a
@CacheFlush
and will therefore not be cached at all.
- Dynamically scaffolded actions (e.g. edit_, _update_, etc.) will use the class level annotation and their results will be cached in the _albumControllerCache cache.
Content caching in the Springcache plugin attempts to respect any cache-control headers present in the original response.
Specifically the plugin will handle certain response headers as follows:Cache-Control: no-cache
If this header is present in the response the content will not be cached even if there is a @Cacheable
annotation
present on the controller or action. This allows you to prevent caching in certain circumstances or override the
controller-wide caching policy in a particular action.Cache-Control: max-age=x
If the response is cached the time-to-live of the cache entry is set so that it corresponds to the max-age value in
the Cache-Control
header. If no such header is present the cache's configured time-to-live is used (see Cache Configuration).ETag
If the original response set an ETag
header Springcache will set the same header if it serves the response from the
cache.
Additionally, if an incoming request has an If-None-Match
header that matches the ETag
of the cached response Springcache will send a 304 Not Modified
status code and an empty response body instead of the cached response.Last-Modified
If the original response set a Last-Modified
header Springcache will set the same header if it serves the response
from the cache.
Additionally, if an incoming request has an If-Modified-Since
header with a timestamp later than the
Last-Modified
header of the cached response Springcache will send a 304 Not Modified
status code and an empty
response body instead of the cached response.The Cache Headers Plugin
The Springcache plugin integrates well with the Cache Headers plugin. Some examples:Preventing caching
If you want to prevent Springcache from caching a response in certain circumstances:@Cacheable("myCache")
def myAction = {
// …
if (someConditionHoldsThatMeansThisShouldNotGetCached) {
cache false
}
// …
}
Alternatively you might want to declare @Cacheable
at the class level and then exclude a particular action from the
cache:@Cacheable("myCache")
class MyController { // ... def myAction = {
cache false
// …
}
Controlling cache expiry
As explained above cache time-to-live will respect the max-age in a cache control header.@Cacheable("myCache")
def myAction = {
cache validFor: 3600
// …
}
In this example the response will be cached with a time-to-live of one hour regardless of the default time-to-live
configured on the cache itself.Sending Not-Modified responses
@Cacheable("myCache")
def show = {
withCacheHeaders {
def book = Book.get(params.id)
etag {
"${book.ident()}:${book.version}"
}
lastModified {
book.dateCreated ?: book.dateUpdated
}
generate {
render view: "show", model: [item: book]
}
}
}
In this example the response will be cached and any subsequent requests that send matching If-Modified-Since
and/or
If-None-Match
headers will be sent a 304 Not Modified
response if they hit the cache.
The Springcache plugin uses an instance of the interface grails.plugin.springcache.key.KeyGenerator
to generate the cache key. The default implementation is a bean named springcacheDefaultKeyGenerator which is of type grails.plugin.springcache.web.key.DefaultKeyGenerator
. If you want to use a different key generator for a particular action you just need to add the keyGenerator
element to the @Cacheable
annotation specifying the name of a Spring bean that implements the KeyGenerator
interface.@Cacheable(cache = "albumControllerCache", keyGenerator = "myKeyGenerator")
def list = {
// …
}
Alternatively you can override the default key generator by redefining the springcacheDefaultKeyGenerator bean in _resources.groovy_.
The keyGenerator
element is only for content caching and just works on controllers, it is ignored by the @Cacheable
annotation on service methods and taglibs.
grails.plugin.springcache.web.key.DefaultKeyGenerator
The DefaultKeyGenerator
generates a key based on the controller name, action name and any request parameters (which can be from a query string, POST body or those added by Grails URL mappings, e.g. the id parameter on a standard show or edit action).grails.plugin.springcache.web.key.WebContentKeyGenerator
WebContentKeyGenerator
is a multi-purpose KeyGenerator
implementation that exposes a number of boolean properties that control key generation. All the properties default to false_.ajax
If _true then keys will differ depending on the presence or absence of the X-Requested-With
request header so AJAX requests will be cached separately from regular requests. This is useful when you have an action that renders different content when it is requested via AJAX_.contentType
If _true keys will differ depending on the requested content format as determined by the format
meta-property on HttpServletRequest
. This is useful when you use content negotiation in a request so that responses with different formats are cached separately.See Content Negotiation for more detail.requestMethod
If true keys will differ depending on the request HTTP method. This is useful for some RESTful controllers (although if different request methods are mapped to different actions you do not need to use this mechanism). GET and HEAD requests are considered the same for the purposes of key generation.Example configuration
ajaxAwareKeyGenerator(WebContentKeyGenerator) {
ajax = true
}contentTypeAwareKeyGenerator(WebContentKeyGenerator) {
contentType = true
}
By default the key generator used by the page fragment caching filter does not take content negotiation into account. However, if you are caching controller actions that use Grails' withFormat
dynamic method to render different content types you will want to cache results separately according to the output format. You can use the WebContentKeyGenerator
class to do this. You just need to register a key generator bean with Spring and then annotate any content negotiated actions like this:grails-app/conf/spring/resources.groovy
mimeTypeAwareKeyGenerator(WebContentKeyGenerator) {
contentType = true
}
grails-app/controllers/MyController.groovy
@Cacheable(cache = "albumControllerCache", keyGenerator = "mimeTypeAwareKeyGenerator")
def list = {
def albumList = Album.list()
withFormat {
html { [albumList: albumList] }
xml { render albumList as XML }
json { render albumList as JSON }
}
}
The plugin only provides page fragment caching rather than full page caching. Full page caching is very simple to apply using the EhCache-Web library that the Springcache plugin uses. See my blog post here for details.
An alternative to using includes for caching smaller page fragments is to use caching on taglib tags. When a cacheable tag is called, the parameters it is called with from the cache key. If there is no entry in the cache, the tag is executed and the output that it generated is cached (as well as being written to the page). If there is an entry in the cache, the tag is not executed and the cached output is written to the page.class BlogArticlesTagLib { static namespace = "blogarticles" def blogArticlesService @Cacheable("blogArticlesTagCache")
def allArticles = { attrs ->
out << "<ul>"
blogArticlesService.getArticles(attrs.id).each {
out << "<li>${it.title}</li>"
}
out << "</ul>"
}
}
When we call the tag like so…<blogarticles:allArticles id="${blogId}" />
The cache key is formed by the 'id' tag. This tag can be reused across different views without changing the caching semantics. That is, the controller/action that the cacheable tag is called from does not affect the cache key.Tags with a body
When caching a tag with a body, if there is a cache hit the body will not be executed. Therefore it doesn't make sense to cache a tag that is invoked with a different body unless you are ensuring the right cacheability through the tag parameters (i.e. cache keys).